/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sistemacalidad;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;

/**
 *
 * @author jorgeorm
 */
public class SistemaCalidad {
    
    public StringBuilder sb_salidaSistema;
    
    public GeneradorAleatorioStandardMinimo generadorAleatorio;
    /*
     * obreroOcupad: Indica si el obrero está o no ocupado - V. Estado.
     * inspectorOcupado: Indica si el obrero está o no ocupado - V. Estado.
     */
    boolean obreroOcupado, inspectorOcupado;
    
    /* detallada de un producto - Variable entrada.
     * num_inspec: Total de elementos inspeccionados por el inspector - V. Desempeño
     * num_ajust: Total de elementos que requirieron ajuste - V. Desempeño
     * num_generados: Total de productos generados por la fábrica - V. Desempeño
     * num_prodEnColaAjust: Numero de productos que esperan ser atendidos por los obreros - V. Estado
     * reloj: Reloj del sistema (Segundos)
     * max_cola_ajuste: Maximo valor alcanzado por la cola de productos a ajustar durante la simulación - V. Desempeño
     * t_obrero_ocup: Tiempo que el obrero está ocupado - V. Desempeño.
     * t_inspec_ocup: Tiempo que el inspector está ocupado - V. Desempeño.
     * num_prodColaInspec: Numero de elementos en la cola de inspeccion - V. Estado
     * max_cola_inspec: Numero maximo de elemntos en la cola de inspección
     * num_InspecDisp: Número de inspectores ocupados que se permiten. V. Configuracion
     * num_ObreroDisp: Número de obreros ocupados que se permiten. V. Configuracion.
     * cont_InspecOcup: Contador de inspectores ocupados en el momento. V. Estado.
     * cont_ObreroOcup: Contador de obreros ocupados. V. Estado.
     * t_supAjuste: Cota superior que toma el ajuste de un producto en segundos.V. Configuración
     * t_infAjuste: Cota inferior que toma el ajuste de un producto en segundos.V. Configuración
     * acum_tiempProd: Acumulador del tiempo que los productos pasan en el sistema; V. Desempeño
     */
    int t_produc, num_inspec, 
            num_ajust, num_generados, num_prodEnColaAjust, reloj,
            max_cola_ajuste, t_obrero_ocup, t_inspec_ocup, num_prodColaInspec,
            max_cola_inspec, num_InspecDisp, num_ObreroDisp, cont_InspecOcup,
            cont_ObreroOcup, t_supAjuste, t_infAjuste, acum_tiempoProd;
    
    /*Configuración del proeblema
     */
    int t_mediaInspec, t_desvEstInspec;
    
    /*
     * p_tiempoInspec, p_tiempoObrero: Porcentaje del tiempo que las respectivas entidades de servicio estuvieron
     * ocupadas. - V. Desempeño
     * prob_ajuste Probabilidad de un producto de ser ajustado - V. configuracion
     * prob_revision Probabilidad de ser revisado
     */
    double p_tiempoInspec, p_tiempoObrero, prob_ajuste, prob_revision;
    
    /*Representa el listado de productos que se tiene en la banda
     */
    ArrayList<Integer> listInicioInspec, colaInspeccion, colaAjuste, listFinInspec;
    
    /*Estado
     */
    String eventoActual ="";
    
    /*Lista de eventos futuros
     */
    PriorityQueue<Evento> LEF;
    
    public SistemaCalidad(){
        /*Creo la lista de eventos futuros
         */
        LEF = new PriorityQueue(1, new ComparadorEvento());
        int semilla = (int) System.currentTimeMillis();
        generadorAleatorio = new GeneradorAleatorioStandardMinimo(semilla);
        sb_salidaSistema = new StringBuilder();
        listInicioInspec = new ArrayList();
        listFinInspec = new ArrayList();
        colaInspeccion = new ArrayList();
        colaAjuste = new ArrayList();
    }
    
    public double generarRevisionProducto(){
        double p;
        p=generadorAleatorio.nextDouble();
        //System.out.println(p+" ");
        return p;
    }
    
    /*Intervalo de tiempo en minutos
     */
    public int generarTiempoAjuste(int a, int b){
        double diferencia = (b-a);
        diferencia *= generadorAleatorio.nextDouble();
        diferencia += a;
        
        return (int) diferencia;
    }
    
    public int generarTiempoInspeccion(int media, int desviacion){
        
        double tiempo = generadorAleatorio.nextGaussian();
        
        tiempo *= (double)desviacion;
        tiempo += (double)media;
                
        return (int) tiempo;
    }
    
    /*EVENTOS:::::::::::::::::::::::::::::::::::::::::::::::::
    /*
    * Genera un producto nuevo y programa su entrada al punto de inspeccion en el tiempo escogido
    */
    
    public void entradaProductoNuevo(){
        eventoActual = "EPN";
        
        /*Genero la próxima entrada de producto nuevo
         */
        LEF.add(new Evento(reloj+t_produc, eventoActual));
        
        /*Añado el paso por el punto de inspección si es seleccionable
         */
        LEF.add(new Evento(reloj+1, "EPI"));
        num_generados++;
    }
    
    /*
    * Si el inspector esta ocupado el producto sale de la banda, de lo contrario 
    * pasa al inspector para evaluar si será o no revisado
    */
    
    public void entradaPuntoInspeccion( int idProducto ){
        eventoActual = "EPI";
        double p_revision = generarRevisionProducto();
        int t_inspec;
        int idProd = idProducto;
        
        
        if(p_revision < this.prob_revision){
            /*Verifico si el producto es nuevo o ya tiene ID
             */
            if(idProd==-1){
                idProd = listInicioInspec.size();
                listInicioInspec.add(reloj);
                listFinInspec.add(-1);
            }
            /*Verifico que el obrero esté libre
             */
//            if(inspectorOcupado){
            if(cont_InspecOcup == num_InspecDisp){
                num_prodColaInspec++;
                colaInspeccion.add(idProd);
                if(num_prodColaInspec > max_cola_inspec){
                    max_cola_inspec = num_prodColaInspec;
                }
            }else {
//                inspectorOcupado = true;
                cont_InspecOcup++;
                t_inspec = generarTiempoInspeccion(t_mediaInspec, t_desvEstInspec);
                t_inspec_ocup += t_inspec;/*Variable desempeño*/
                LEF.add(new Evento(reloj+t_inspec, "SPI", idProd));
            }
        }else {
            LEF.add(new Evento(reloj+1,"SPB", idProd));
        }        
    }
    
    /*
     * Tras revisar se determina si es necesario hacer ajuste o no si es necesario se manda
     * a ajuste y se desocupa el inspector para revisar otros posibles productos.
     * 
     */
    public void salidaProductoInspeccion(int idProd){
        eventoActual = "SPI";
        double maldadProD = generarRevisionProducto();
        int t_inspec;
        
        /*Verifico si el producto está malo
         */
        if(maldadProD < prob_ajuste){
            LEF.add(new Evento(reloj+1, "EPA", idProd));
        }else {
            LEF.add(new Evento(reloj+1, "SPB", idProd));
        }
        
        if(num_prodColaInspec > 0){
            /*Saco producto de la cola de inspección
             */
            num_prodColaInspec--;
            
            /*Atiendo producto sacado de la cola
             */
            t_inspec = generarTiempoInspeccion(t_mediaInspec, t_desvEstInspec);
            t_inspec_ocup += t_inspec;/*Variable desempeño*/
            LEF.add(new Evento(reloj+t_inspec, "SPI", colaInspeccion.remove(0)));
            
        }else {
//            inspectorOcupado = false;
            cont_InspecOcup--;
        }
    }
    
    
    /*Si el obrero está ocupado el producto entra a cola de espera, si no el producto 
     * pasa a entidad de servicio obrero.
     */
    public void entradaPuntoAjuste(int idProd){
        eventoActual = "EPA";
        int t_ajuste;
        
//        if(obreroOcupado){
        if(cont_ObreroOcup == num_ObreroDisp){
            num_prodEnColaAjust++;
            colaAjuste.add(idProd);
            if(num_prodEnColaAjust > max_cola_ajuste){
                max_cola_ajuste = num_prodEnColaAjust;
            }
        }else{
//            obreroOcupado = true;
            cont_ObreroOcup++;
            /*Atención de producto en cola de ajuste
             */
            t_ajuste = generarTiempoAjuste(t_infAjuste, t_supAjuste);
            t_obrero_ocup += t_ajuste; /*V. Desempeño*/
            LEF.add(new Evento(reloj+t_ajuste, "SPA", idProd));
        }
        
    }
    
    /*Si hay productos en cola de espera, sacar producto de cola de espera y 
     * comenzar ajuste. Si no, el obrero entra a estar desocupado.
     */
    public void salidaProductoAjuste(int idProd){
        eventoActual = "SPA";
        int t_ajuste;
        
        /*Vuelvo a meterlo a la banda de inspección
         */
        num_ajust++;//V. Desempeño
        LEF.add(new Evento(reloj+1, "EPI", idProd));
        
        if(num_prodEnColaAjust>0){
            /*Atención de producto en cola de ajuste
             */
            t_ajuste = generarTiempoAjuste(5, 10);
            t_obrero_ocup += t_ajuste;/*V. Desempeño*/
            num_prodEnColaAjust--;
            LEF.add(new Evento(reloj+t_ajuste, "SPA"));
            
        }else {
//            obreroOcupado = false;
            cont_ObreroOcup--;
        }
        
    }
    
    /*Es el evento final de un producto al salir del sistema
     */
    public void salidaProductoBanda(int idProd){
        eventoActual = "SPB";
        if(idProd>-1){
            num_inspec++;/*Variable de desempeño, cuenta los productos que ya salen del proceso
             de inspección*/
            listFinInspec.set(idProd, reloj);
        }
    }
    
    /*UTILS
     */
    public void almacenarEstadoSistema(){
        sb_salidaSistema.append(reloj);
        sb_salidaSistema.append("\t,");
        sb_salidaSistema.append(eventoActual);
        sb_salidaSistema.append("\t,");
        sb_salidaSistema.append(num_prodColaInspec);
        sb_salidaSistema.append("\t,");
        sb_salidaSistema.append(num_prodEnColaAjust);
        sb_salidaSistema.append("\t");
        sb_salidaSistema.append(LEF.toString());
        sb_salidaSistema.append("\n");
    }
    
    public void calcularTiempoPromedioInspec(){
        StringBuilder sb = new StringBuilder();
        sb.append("Inicio,\tFin\t,Tiempo\n");
        int t_inspecProd = 0;
        
        t_mediaInspec = 0;
        
        for (int i = 0; i < listInicioInspec.size(); i++) {
            t_inspecProd = listFinInspec.get(i) - listInicioInspec.get(i);
            if(t_inspecProd > 0){
                t_mediaInspec += t_inspecProd;
//                sb.append(listInicioInspec.get(i));
//                sb.append(",\t");
//                sb.append(listFinInspec.get(i));
//                sb.append(",\t");
//                sb.append(t_inspecProd);
//                sb.append(",\n");
            }
        }
        
//        sb.append("Suma Tiempos Inspeccion: ");
//        sb.append(t_mediaInspec);
//        sb.append("\n");
//        
//        sb.append("Numero Productos Inspeccionados: ");
//        sb.append(num_inspec);
//        sb.append("\n");
//        
//        sb.append("Numero Productos Generados: ");
//        sb.append(num_generados);
//        sb.append("\n");
        
        t_mediaInspec = (int) Math.round((double) t_mediaInspec / (double)num_inspec);
        
        sb.append("Tiempo Promedio de producto en inspección (minutos): ");
        sb.append(Math.round((double)t_mediaInspec/60.0));
        sb.append("\n");
        
        System.out.print(sb.toString());
        
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String nomEvent;
        int idProducto;
        
        
        SistemaCalidad objSistema = new SistemaCalidad();
        
        objSistema.reloj = 0;
        objSistema.LEF.add(new Evento(3, "EPN"));
//        objSistema.inspectorOcupado = false;
//        objSistema.obreroOcupado = false;
        objSistema.num_prodEnColaAjust = 0;
        objSistema.t_produc = 3;
        objSistema.t_mediaInspec = 30;
        objSistema.t_desvEstInspec = 5;
        objSistema.prob_ajuste = 0.05;
        objSistema.prob_revision = 0.15;
        objSistema.num_InspecDisp = 1;
        objSistema.num_ObreroDisp = 1;
        objSistema.t_supAjuste = 10*60;
        objSistema.t_infAjuste = 5*60;
        
//        System.out.println("reloj,\tevento,\tpilIns,\tpilAju,\tLEF");
        
        /*String Builder para verificar los datos generados
         */
        StringBuilder sb = new StringBuilder();
        
        Evento eventoTmp = null;
        /*Configuro para que el reloj funcione con una jornada laboral de 8 horas
         */
        while(objSistema.reloj <= 16800){
            eventoTmp = objSistema.LEF.poll();
            
            objSistema.reloj = eventoTmp.tiempo;
            nomEvent = eventoTmp.nombreEvento;
            idProducto = eventoTmp.idProducto;
            
            /*Ejecuto evento próximo
             */
            if(nomEvent.equals("EPN")){
                objSistema.entradaProductoNuevo();
            }else if(nomEvent.equals("EPI")){
                objSistema.entradaPuntoInspeccion(idProducto);
            }else if(nomEvent.equals("SPI")){
                objSistema.salidaProductoInspeccion(idProducto);
            }else if(nomEvent.equals("EPA")){
                objSistema.entradaPuntoAjuste(idProducto);
            }else if(nomEvent.equals("SPA")){
                objSistema.salidaProductoAjuste(idProducto);
            }else if(nomEvent.equals("SPB")){
                objSistema.salidaProductoBanda(idProducto);
            }
            /*Pego el número aleatorio generado para bondad del generador
             */
//            sb.append(objSistema.generadorAleatorio.nextGaussian()+"\n");
//            objSistema.almacenarEstadoSistema();
        }
        /*Imprimo como fue el funcionamiento del sistema
         */
//        System.out.println(objSistema.sb_salidaSistema.toString());
//        System.out.println("Generador aleatorio:");
//        System.out.print(sb.toString());
        double razon_tOcup;
        
        objSistema.calcularTiempoPromedioInspec();
        
        razon_tOcup = (double)objSistema.t_obrero_ocup/(double)(objSistema.reloj*objSistema.num_ObreroDisp);
        objSistema.p_tiempoObrero = Math.round(1000.0*razon_tOcup)/10.0;
        
        System.out.println("Porcentaje tiempo ocupado del obrero : "+objSistema.p_tiempoObrero+"%");
        
        razon_tOcup = (double)objSistema.t_inspec_ocup/(double)(objSistema.reloj*objSistema.num_InspecDisp);
        objSistema.p_tiempoInspec = Math.round(1000.0*razon_tOcup)/10.0;
        
        System.out.println("Porcentaje tiempo ocupado del Inspector: "+objSistema.p_tiempoInspec+"%");
        
        System.out.println("Cola maxima alcanzada para ajustes: "+objSistema.max_cola_ajuste);
        
        System.out.println("Cola maxima alcanzada para ser inspeccionado: "+objSistema.max_cola_inspec);
        
        System.out.println("Cola de inspección al momento de finalizar: "+objSistema.num_prodColaInspec);
        
        System.out.println("Cola de ajuste al momento de finalizar: "+objSistema.num_prodEnColaAjust);
        
        sb.append("Numero Productos Inspeccionados: ");
        sb.append(objSistema.num_inspec);
        sb.append("\n");
        
        sb.append("Numero Productos Generados: ");
        sb.append(objSistema.num_generados);
        sb.append("\n");
        
        System.out.print(sb.toString());
        
    }
}



class Evento{
    public int tiempo;
    public String nombreEvento;
    public int idProducto;

    public Evento(int tiempo, String nombre, int idProd) {
        this.tiempo = tiempo;
        nombreEvento = nombre;
        this.idProducto = idProd;
    }
    
    public Evento(int tiempo, String nombre) {
        this.tiempo = tiempo;
        nombreEvento = nombre;
        this.idProducto = -1;
    }

    @Override
    public String toString() {
        return "{"+tiempo+";"+nombreEvento+";"+idProducto+"}";
    }
    
    
}

class ComparadorEvento implements Comparator<Evento>{

    @Override
    public int compare(Evento o1, Evento o2) {
            if(o1.tiempo < o2.tiempo) {
                return -1;
            }
            else if(o1.tiempo == o2.tiempo) {
                return 0;
            } else {
                return 1;
            }
    }
}
